--[[
* @author PureMVC LUA Native Port by Ravior 
* @author Copyright(c) 2015 Gitlib, Inc., Some rights reserved.
* 
* @class puremvc.View
* 
* A Multiton View implementation.
* 
* In PureMVC, the View class assumes these responsibilities
* 
* - Maintain a cache of {@link puremvc.Mediator Mediator}
*   instances.
* 
* - Provide methods for registering, retrieving, and removing 
*   {@link puremvc.Mediator Mediator}.
* 
* - Notifiying {@link puremvc.Mediator Mediator} when they are registered or 
*   removed.
* 
* - Managing the observer lists for each {@link puremvc.Notification Notification}  
*   in the application.
* 
* - Providing a method for attaching {@link puremvc.Observer Observer} to an 
*   {@link puremvc.Notification Notification}'s observer list.
* 
* - Providing a method for broadcasting a {@link puremvc.Notification Notification}.
* 
* - Notifying the {@link puremvc.Observer Observer}s of a given 
*   {@link puremvc.Notification Notification} when it broadcast.
* 
* This View implementation is a Multiton, so you should not call the 
* constructor directly, but instead call the static Multiton 
* Factory #getInstance method
]]
local Observer = import('..patterns.observer.Observer')
local View = class("View")


--[[
* @param {string} key
* @constructor
* @throws {Error} 
*  if instance for this Multiton key has already been constructed
]]
function View:ctor(key)
	if View.instanceMap[key] ~= nil then
		error(View.MULTITON_MSG)
	end
	self.multitonKey = key
	View.instanceMap[self.multitonKey] = self
	self.mediatorMap = {}
	self.observerMap = {}
	self:initializeView()
end


--[[
* @protected
* Initialize the Singleton View instance
* 
* Called automatically by the constructor, this is your opportunity to
* initialize the Singleton instance in your subclass without overriding the
* constructor
* 
* @return {void}
]]
function View:initializeView() end


--[[
* View Singleton Factory method.
* Note that this method will return null if supplied a null 
* or undefined multiton key.
*  
* @return {puremvc.View}
*  The Singleton instance of View
]]
function View:getInstance()
	if nil == key then
		return nil
	end
	if View.instanceMap[key] == nil then
		return View.new(key)
	else
		return View.instanceMap[key]		
	end
end


--[[
* Register an Observer to be notified of Notifications with a given name
* 
* @param {string} notificationName
*  The name of the Notifications to notify this Observer of
* @param {puremvc.Observer} observer
*  The Observer to register.
* @return {void}
]]
function View:registerObserver(notificationName,observer)
	if self.observerMap[notificationName] ~= nil then
		table.insert(self.observerMap[notificationName], observer)
	else
		self.observerMap[notificationName] = {observer}
	end
end


--[[
* Notify the Observersfor a particular Notification.
* 
* All previously attached Observers for this Notification's
* list are notified and are passed a reference to the INotification in 
* the order in which they were registered.
* 
* @param {puremvc.Notification} notification
*  The Notification to notify Observers of
* @return {void}
]]
function View:notifyObservers(notification)
	if self.observerMap[notification:getName()] ~= nil then
		local observers_ref = self.observerMap[notification:getName()]
		for _,o in pairs(observers_ref) do
			o:notifyObserver(notification)
		end
	end
end


--[[
* Remove the Observer for a given notifyContext from an observer list for
* a given Notification name
* 
* @param {string} notificationName
*  Which observer list to remove from
* @param {Object} notifyContext
*  Remove the Observer with this object as its notifyContext
* @return {void}
]]
function  View:removeObserver(notificationName,notifyContext)
	local observers = self.observerMap[notificationName]
	for _,o in pairs(observers) do
		if o:compareNotifyContext(context) then
			table.remove(observers,_)
			break
		end
	end

	if #observers == 0 then
		self.observerMap[notificationName] = nil
	end
end


--[[
* Register a Mediator instance with the View.
* 
* Registers the Mediator so that it can be retrieved by name,
* and further interrogates the Mediator for its 
* {@link puremvc.Mediator#listNotificationInterests interests}.
*
* If the Mediator returns any Notification
* names to be notified about, an Observer is created encapsulating 
* the Mediator instance's 
* {@link puremvc.Mediator#handleNotification handleNotification}
* method and registering it as an Observer for all Notifications the 
* Mediator is interested in.
* 
* @param {puremvc.Mediator} 
*  a reference to the Mediator instance
]]
function View:registerMediator(mediator)
	if self.mediatorMap[mediator:getMediatorName()] ~= nil then
		return
	end

	mediator:initializeNotifier(self.multitonKey)
	-- register the mediator for retrieval by name
	self.mediatorMap[mediator:getMediatorName()] = mediator

	-- get notification interests if any
	local interests = mediator:listNotificationInterests()

	-- register mediator as an observer for each notification
	if #interests > 0 then
		-- create observer referencing this mediators handleNotification method
		local observer = Observer.new(mediator.handleNotification,mediator)
		for _,i in pairs(interests) do
			self:registerObserver(i,observer)
		end
	end

	mediator:onRegister()
end


--[[
* Retrieve a Mediator from the View
* 
* @param {string} mediatorName
*  The name of the Mediator instance to retrieve
* @return {puremvc.Mediator}
*  The Mediator instance previously registered with the given mediatorName
]]
function View:retrieveMediator(mediatorName)
	return self.mediatorMap[mediatorName]
end


--[[
* Remove a Mediator from the View.
* 
* @param {string} mediatorName
*  Name of the Mediator instance to be removed
* @return {puremvc.Mediator}
*  The Mediator that was removed from the View
]]
function View:removeMediator(mediatorName)
	local mediator = self.mediatorMap[mediatorname]
	if mediator ~= nil then
		-- for every notification the mediator is interested in...
		local interests = mediator:listNotificationInterests()
		for _,i in pairs(interests) do
			-- remove the observer linking the mediator to the notification
            -- interest
			self:removeObserver(i,mediator)
		end

		-- remove the mediator from the map
		self.mediatorMap[mediatorName] = nil

		-- alert the mediator that it has been removed
		mediator:onRemove()
	end

	return mediator
end


--[[
* Check if a Mediator is registered or not.
* 
* @param {string} mediatorName
* @return {boolean}
*  Whether a Mediator is registered with the given mediatorname
]]
function View:hasMediator(mediatorName)
	return self.mediatorMap[mediatorName] ~= nil
end


--[[
* Remove a View instance
* 
* @return {void}
]]
function View.removeView(key)
	View.instanceMap[key] = nil
end


--[[
* @ignore
* The map used by the Model to store multiton instances
*
* @protected
* @static
* @type Array
]]
View.instanceMap = {}


--[[
* @ignore
* The error message used if an attempt is made to instantiate View directly
*
* @type string
* @protected
* @const
* @static
]]
View.MULTITON_MSG = "View instance for this Multiton key already constructed!"

return View
